/*-
 * Copyright (C) 2018-2020, Shanghai Trusted Industry Control Platform Co., Ltd.
 * Created by 周正辉 (Zhenghui Zhou) <zhouzhenghui@ticpsh.com>, 赵楷文 (Kaiwen
 * Zhao) <zhaokaiwen@ticpsh.com>
 */

#include "testhelper.h"

#include "common/Coder.h"

using namespace rocket::common;
using namespace std;
using namespace clang;
using namespace llvm;
namespace fs = boost::filesystem;

/**
 * @brief source_location_string
 * get source code location
 * @param sm
 * @param loc
 * @return
 */
std::string rocket::testif::source_location_string(const SourceManager &sm,
                                                   SourceLocation loc) {
  PresumedLoc presumed_loc = sm.getPresumedLoc(loc);

  if (presumed_loc.isInvalid()) return "<invalid sloc>";

  std::string filename = presumed_loc.getFilename();
  std::string current_path = fs::current_path().string();
  if (filename.size() > current_path.size() &&
      filename.compare(0, current_path.size(), current_path) == 0) {
    filename =
        fs::path(filename.substr(current_path.size())).relative_path().string();
  }
  return filename + ":" + std::to_string(presumed_loc.getLine()) + ":" +
         std::to_string(presumed_loc.getColumn());
}

string rocket::testif::getNamedDeclName(const NamedDecl *nd) {
  if (auto dn = nd->getDeclName()) {
    return dn.getAsString();
  } else {
    return "(anonymous" + (nd->getSourceRange().isValid()
                               ? "@" +
                                     source_location_string(
                                         nd->getASTContext().getSourceManager(),
                                         nd->getBeginLoc()) +
                                     ")"
                               : ")");
  }
}

std::vector<string> rocket::testif::getFuncAttrs(const FunctionDecl *fd) {
  vector<std::string> attributes;
  if (!fd) return attributes;

  if (fd->isVariadic()) {
    attributes.push_back("isVariadic");  // 变参
  }

  if (fd->isNoReturn()) {
    // __attribute__((__noreturn)) 或 [[noreturn]]
    attributes.push_back("isNoReturn");
  }

  if (fd->getDefinition()) {
    attributes.push_back("isDefined");
  }

#if LLVM_VERSION_MAJOR >= 9
  if (fd->isStatic()) {
    attributes.push_back("isStatic");  // 静态
  }
#else
  if (fd->getStorageClass() == SC_Static) {
    attributes.push_back("isStatic");  // 静态
  }
#endif

  if (fd->isTemplated()) {
    attributes.push_back("isTemplate");  // 模板
  }
  if (fd->isFunctionTemplateSpecialization()) {
    attributes.push_back("isTemplateSpecialization");
  }

  if (const CXXMethodDecl *method_decl = dyn_cast<CXXMethodDecl>(fd)) {
    if (method_decl->getTemplatedKind() ==
        FunctionDecl::TK_MemberSpecialization) {
      attributes.push_back("isTemplateSpecialization");
    }

    // 为类方法时的属性标签
    if (const CXXConstructorDecl *constructor =
            dyn_cast<CXXConstructorDecl>(method_decl)) {
      attributes.push_back("isConstructor");  // 构造函数

      if (constructor->isCopyConstructor())
        attributes.push_back("isCopyConstructor");  // 拷贝构造
      if (constructor->isMoveConstructor())
        attributes.push_back("isMoveConstructor");  // 移动构造函数
      if (constructor->isDefaultConstructor())
        attributes.push_back("isDefaultConstructor");  // 默认构造函数
      if (constructor->isConvertingConstructor(constructor->isExplicit()))
        attributes.push_back("isConvertingConstructor");  // 转换构造
      if (constructor->isDelegatingConstructor())
        attributes.push_back("isDelegatingConstructor");  // 委托构造
      if (constructor->isInheritingConstructor())
        attributes.push_back("isInheritingConstructor");  // 继承构造

    } else {
      attributes.push_back("isMethod");
    }

    if (isa<CXXConversionDecl>(method_decl)) {
      // NOTE:类型转换操作读重载
      attributes.push_back("isConversion");
    } else if (isa<CXXDestructorDecl>(method_decl)) {
      // 析构函数
      attributes.push_back("isDestructor");
    }

    if (method_decl->isConst()) {
      attributes.push_back("isConst");  // 后缀，const限定
    }
    if (method_decl->isVirtual()) {
      attributes.push_back("isVirtual");  // 虚函数
    }
#if LLVM_VERSION_MAJOR < 18
    if (method_decl->isPure()) {
      attributes.push_back("isPureVirtual");  // 虚函数
    }
#else
    if (method_decl->isPureVirtual()) {
      attributes.push_back("isPureVirtual");  // 虚函数
    }
#endif
    if (method_decl->isDefaulted()) {
      attributes.push_back("isDefaulted");  // 默认函数
    }
    if (method_decl->isDeleted()) {
      attributes.push_back("isDeleted");  // 删除，实际不会出现
    }
    if (method_decl->isOverloadedOperator()) {
      attributes.push_back("isOverloadedOperator");
    }
  }
  return attributes;
}

bool rocket::testif::isContainAttr(const nlohmann::json &attributes,
                                   const string &attribute) {
  for (auto &attr : attributes) {
    if (attr == attribute) return true;
  }
  return false;
}

static void getCodeLineInfoInRange_re(rocket::testif::CodeLineInfo &info,
                                      clang::SourceLocation current,
                                      clang::SourceLocation end,
                                      clang::ASTContext &ast) {
  SourceLocation tok_loc = current;
  auto &sm = ast.getSourceManager();

  unsigned int l_current = 0;

  while (tok_loc < end) {
    if (current.isMacroID()) {
      if (!Lexer::isAtEndOfMacroExpansion(current, sm, ast.getLangOpts(),
                                          &current))
        return;
    }

    // Break down the source location.
    std::pair<FileID, unsigned> locInfo = sm.getDecomposedLoc(current);

    // Try to load the file buffer.
    bool invalidTemp = false;
    clang::StringRef file = sm.getBufferData(locInfo.first, &invalidTemp);
    if (invalidTemp) return;

    const char *tokenBegin = file.data() + locInfo.second;

    // Lex from the start of the given location.
    Lexer lexer(sm.getLocForStartOfFile(locInfo.first), ast.getLangOpts(),
                file.begin(), tokenBegin, file.end());
    Token tok;
    lexer.LexFromRawLexer(tok);

    tok_loc = tok.getLocation();

    auto tok_loc_line = sm.getPresumedLoc(tok_loc);
    if (!tok.is(tok::comment) && !tok.is(tok::eof)) {
      auto l = tok_loc_line.getLine();
      if (l != l_current) {
        info.addCodeLine(l);
        l_current = l;
      }
    }

    current =
        Lexer::getLocForEndOfToken(tok.getLocation(), 0, sm, ast.getLangOpts());
  }
}

rocket::testif::CodeLineInfo rocket::testif::getCodeLineInfoInRange(
    clang::SourceRange range, clang::ASTContext &ast) {
  CodeLineInfo info;

  auto &sm = ast.getSourceManager();
  auto begin = range.getBegin();
  auto end = range.getEnd();
  auto ploc_begin = sm.getPresumedLoc(begin);
  auto ploc_end = sm.getPresumedLoc(end);

  getCodeLineInfoInRange_re(info, begin, end, ast);

  return info;
}
